package phasebook.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.Singleton;
import javax.ejb.Stateless;

import phasebook.client.DirtyRegionManager;
import phasebook.client.DirtyRegionManager.DirtyRegion;

@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class DirtyRegionManagerImpl implements DirtyRegionManager{
	private Hashtable<Long,Subscriber> subscribers = new Hashtable<Long,Subscriber>();
	
	public class Subscriber{
		private long myId;
		private boolean hasDirtyRegion=false;
		private HashMap<Long,HashMap<Region,DirtyRegionSubscription>> subscriptions = new HashMap<Long,HashMap<Region,DirtyRegionSubscription>>();
		
		public Subscriber(long myId) {
			this.myId = myId;
		}
		

		public synchronized void subscribe(long userId, Region... regions) {
			HashMap<Region, DirtyRegionSubscription> subs = subscriptions.get(userId);
			if (subs==null){
				subs = new HashMap<Region, DirtyRegionSubscription>();
				subscriptions.put(userId, subs);
			}
			
			for(Region region:regions){
				DirtyRegionSubscription subscription = subs.get(region);
				if (subscription==null){
					subscription=new DirtyRegionSubscription(userId,region);
					subs.put(region,subscription);
				}
			}
		}
		
		public synchronized void unsubscribe(long userId) {
			subscriptions.remove(userId);
		}

		public synchronized void markDirty(long userId, Region... regions) {
			HashMap<Region, DirtyRegionSubscription> subs = subscriptions.get(userId);
			if (subs!=null){
				for(Region region:regions){
					DirtyRegionSubscription subscription = subs.get(region);
					if (subscription!=null){
						subscription.isDirty = true;
						this.hasDirtyRegion = true;
					}
				}
				if(this.hasDirtyRegion)
					this.notify();
			}
		}

		public synchronized Set<DirtyRegion> waitForDirtyRegions(){
			Collection<HashMap<Region, DirtyRegionSubscription>> subss = subscriptions.values();
			if(!hasDirtyRegion){
				try {
					this.wait(3000);
				} catch (InterruptedException e) {}
			}
			if(hasDirtyRegion){
				HashSet<DirtyRegion> ret = new HashSet<DirtyRegion>();
				for(HashMap<Region, DirtyRegionSubscription> subs: subss)
					for(DirtyRegionSubscription sub: subs.values()){
						if (sub.isDirty){
							ret.add(sub);
							sub.isDirty = false;
						}
					}
				hasDirtyRegion=false;
				return ret;
			}else{
				return null;
			}
		}
			
		public class DirtyRegionSubscription implements DirtyRegion{
			private Region region;
			private boolean isDirty = false;
			private long userId;
			public DirtyRegionSubscription(long userId,Region region) {
				this.userId = userId;
				this.region = region;
			}
			@Override
			public Region getRegion() {
				return region;
			}
			@Override
			public long getUserId() {
				return userId;
			}
			@Override
			public long getMyId() {
				return myId;
			}
			public boolean isDirty() {
				return isDirty;
			}
			public void setDirty(boolean isDirty) {
				this.isDirty = isDirty;
			}
			public String toString(){
				return "\t\t\t[R:"+region+",uId:"+userId+",dirty:"+isDirty+"]\n";
			}
		}
	}
	
	private synchronized Subscriber createOrGetSubscriber(long id){
		Subscriber subscriber = subscribers.get(id);
		if (subscriber==null){
			subscriber = new Subscriber(id);
			subscribers.put(id, subscriber);
		}
		return subscriber;
	}
	
	

	@Override
	public Set<DirtyRegion> waitForDirtyRegions(long userId) {
		return createOrGetSubscriber(userId).waitForDirtyRegions();
	}

	@Override
	public void publishDirtyRegions(long userId, Region... regions) {
		ArrayList<Subscriber> subs;
		synchronized(this){
			subs = new ArrayList<Subscriber>(subscribers.values());
		}
		
		for(Subscriber subscriber : subs)
			subscriber.markDirty(userId,regions);
	}

	@Override
	public void subscribeSelf(long myId) {
		createOrGetSubscriber(myId).subscribe(myId,Region.values());
	}

	@Override
	public void subscribeOtherUser(long myId,long userId) {
		createOrGetSubscriber(myId).subscribe(userId,Region.Board,Region.Profile);
	}

	@Override
	public void unsubscribeAll(long myId) {
		subscribers.remove(myId);
	}

	@Override
	public void unsubscribeOtherUser(long myId, long userId) {
		subscribers.get(myId).unsubscribe(userId);
	}
}

